﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using COC.Util;
using LuaNetInterface;

namespace COC.Graphics
{
	class SkydomeComponent : IRenderComponent
	{
		#region Var

		/// <summary>
		/// 地球半径
		/// </summary>
		public const float c_WorldRadius = 6378137.0f;

		int m_NumVerts;
		int m_NumTriangles;

		Vector3 m_v3LightDir;
		Vector3 m_v3InvWaveLength;

		float m_fCameraHeight = c_WorldRadius;
		float m_fCameraHeightSq = c_WorldRadius * c_WorldRadius;
		float m_fOuterRadius = c_WorldRadius * 1.025f;
		float m_fOuterRadiusSq = c_WorldRadius * c_WorldRadius * 1.025f * 1.025f;
		float m_fInnerRadius = c_WorldRadius;
		float m_fInnerRadiusSq = c_WorldRadius * c_WorldRadius;
		float m_fKrESun;
		float m_fKmESun;
		float m_fKr4PI;
		float m_fKm4PI;
		float m_fScale;
		float m_fScaleDepth;
		float m_fScaleOverScaleDepth;
		float m_g;
		float m_gSq;
		float m_fSamples;
		int m_nSamples;

		#endregion

		#region Properties

		/// <summary>
		/// 当前时间,用于计算太阳角度
		/// </summary>
		private DateTime m_TimeOfDay = DateTime.Now;
		public DateTime TimeOfDay
		{
			[LuaFunctionAttribute("getTimeOfDay","")]
			get
			{
				return m_TimeOfDay;
			}
			
			set
			{
				m_TimeOfDay = value;
				CalculateSunPosition();
			}
		}

		[LuaFunctionAttribute("setTimeOfDay", "set hours as float", "time")]
		public void SetHourOfDay(float value)
		{
			m_TimeOfDay = new DateTime(1, 1, 1, (int)(value), (int)((value - (int)value) * 60), 0);
			CalculateSunPosition();
		}

		private Vector2 m_CameraLatitudeLongitude;
		public Vector2 CameraLatitudeLongitude
		{
			
			get
			{
				return m_CameraLatitudeLongitude;
			}
			set
			{
				m_CameraLatitudeLongitude = value;
				CalculateSunPosition();
			}
		}

		/// <summary>
		/// 垂直镶嵌维度
		/// </summary>
		private int m_ThetaDimensions = 64;
		public int ThetaDimensions { get { return m_ThetaDimensions; } set { m_ThetaDimensions = value; } }

		/// <summary>
		/// 水平镶嵌维度
		/// </summary>
		private int m_PhiDimensions = 128;
		public int PhiDimensions { get { return m_PhiDimensions; } set { m_PhiDimensions = value; } }

		#endregion

		#region .ctor
		public SkydomeComponent()
		{
		}
		#endregion

		#region IRenderComponent
		public override void Initialize()
		{
			m_ScatteringEffect = CoCGame.Singleton.Content.Load<Effect>(@"Shaders\SkyScattering");
			VertexElement[] elements = new VertexElement[] {
				new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
			};
			m_VDecl = new VertexDeclaration(CoCGame.Singleton.GraphicsDevice, elements);
			CreateGeometry();
			UpdateParams();
		}

		public override void Render()
		{
			GraphicsDevice device = CoCGame.Singleton.GraphicsDevice;

			Matrix matCentering = Matrix.CreateTranslation(CoCGame.Singleton.GameCamera.Position);
			m_ScatteringEffect.Parameters["matWorld"].SetValue(matCentering);
			m_ScatteringEffect.Parameters["matViewProjection"].SetValue(CoCGame.Singleton.GameCamera.MatViewProj);
			m_ScatteringEffect.Parameters["v3CameraPos"].SetValue(CoCGame.Singleton.GameCamera.Position + new Vector3(0, c_WorldRadius, 0));
			m_ScatteringEffect.Parameters["v3LightDir"].SetValue(m_v3LightDir);
			m_ScatteringEffect.Parameters["v3InvWaveLength"].SetValue(m_v3InvWaveLength);
			m_ScatteringEffect.Parameters["fCameraHeight"].SetValue(m_fCameraHeight);
			m_ScatteringEffect.Parameters["fCameraHeightSq"].SetValue(m_fCameraHeightSq);
			m_ScatteringEffect.Parameters["fOuterRadius"].SetValue(m_fOuterRadius);
			m_ScatteringEffect.Parameters["fOuterRadiusSq"].SetValue(m_fOuterRadiusSq);
			m_ScatteringEffect.Parameters["fInnerRadius"].SetValue(m_fInnerRadius);
			m_ScatteringEffect.Parameters["fInnerRadiusSq"].SetValue(m_fInnerRadiusSq);
			m_ScatteringEffect.Parameters["fKrESun"].SetValue(m_fKrESun);
			m_ScatteringEffect.Parameters["fKmESun"].SetValue(m_fKmESun);
			m_ScatteringEffect.Parameters["fKr4PI"].SetValue(m_fKr4PI);
			m_ScatteringEffect.Parameters["fKm4PI"].SetValue(m_fKm4PI);
			m_ScatteringEffect.Parameters["fScale"].SetValue(m_fScale);
			m_ScatteringEffect.Parameters["fScaleDepth"].SetValue(m_fScaleDepth);
			m_ScatteringEffect.Parameters["fScaleOverScaleDepth"].SetValue(m_fScaleOverScaleDepth);
			m_ScatteringEffect.Parameters["fSamples"].SetValue(m_fSamples);
			m_ScatteringEffect.Parameters["g"].SetValue(m_g);
			m_ScatteringEffect.Parameters["gSq"].SetValue(m_gSq);
			m_ScatteringEffect.Parameters["nSamples"].SetValue(m_nSamples);

			m_ScatteringEffect.Begin();

			device.VertexDeclaration = m_VDecl;
			device.Vertices[0].SetSource(m_SkyDomeVB, 0, sizeof(float) * 3);
			device.Indices = m_SkyDomeIB;

			device.RenderState.DepthBufferEnable = false;

			foreach (EffectPass pass in m_ScatteringEffect.CurrentTechnique.Passes)
			{
				pass.Begin();
				device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, m_NumVerts, 0, m_NumTriangles);
				pass.End();
			}
			device.RenderState.DepthBufferEnable = true;

			m_ScatteringEffect.End();
		}

		public override void Dispose()
		{
			m_ScatteringEffect.Dispose();
			m_VDecl.Dispose();
			m_SkyDomeVB.Dispose();
			m_SkyDomeIB.Dispose();
		}
		#endregion

		#region PrivateMethod
		private void UpdateParams()
		{
			m_nSamples = 8;							// Number of sample rays to use in integral equation
			m_fSamples = (float)m_nSamples;

			float fKr = 0.0025f;					// Rayleigh scattering constant
			float fKm = 0.0015f;					// Mie scattering constant
			float fESun = 15.0f;					// Sun brightness constant
			
			m_fKr4PI = fKr * 4.0f * MathHelper.Pi;
			m_fKm4PI = fKm * 4.0f * MathHelper.Pi;

			m_fKrESun = fKr * fESun;
			m_fKmESun = fKm * fESun;

			m_g = -0.99f;							// The Mie phase asymmetry factor
			m_gSq = m_g * m_g;

			float[] lambda = new float[] { 0.650f, 0.570f, 0.475f };
			m_v3InvWaveLength.X = 1.0f / (float)Math.Pow(lambda[0], 4.0f);
			m_v3InvWaveLength.Y = 1.0f / (float)Math.Pow(lambda[1], 4.0f);
			m_v3InvWaveLength.Z = 1.0f / (float)Math.Pow(lambda[2], 4.0f);

			m_fScaleDepth = 0.25f;
			m_fScale = (float)(1.0 / (m_fOuterRadius - m_fInnerRadius));
			m_fScaleOverScaleDepth = m_fScale / m_fScaleDepth;

			CalculateSunPosition();
		}

		private void CalculateSunPosition()
		{
			float theta = MathHelper.Pi * (m_TimeOfDay.Hour + (float)m_TimeOfDay.Minute / 60 - 12) / 12;
			m_v3LightDir = new Vector3((float)Math.Sin(theta), (float)Math.Cos(theta), 0);
		}

		private void CreateGeometry()
		{
			Vector3 LocalOrigin = new Vector3(0, c_WorldRadius, 0);
			BoundingSphere OuterSphere = new BoundingSphere(Vector3.Zero, m_fOuterRadius);

			m_NumVerts = m_ThetaDimensions * m_PhiDimensions;
			Vector3[] NonHemiSphereVerts = new Vector3[m_NumVerts];
			m_NumTriangles = (m_ThetaDimensions - 1) * (m_PhiDimensions - 1) * 2;
			short[] SphereIndices = new short[m_NumTriangles * 3];

			Vector3 currentXYZ;
			const float thetaFactor = 0.55f;
			float deltaTheta = MathHelper.Pi * thetaFactor / (m_ThetaDimensions - 1);
			float deltaPhi = MathHelper.TwoPi / (m_PhiDimensions - 1);
			float baseTheta = MathHelper.Pi * (0.5f - thetaFactor);

			int k = 0;
			for (int i = 0; i < m_ThetaDimensions; i++)
			{
				float currentTheta = baseTheta + deltaTheta * i;
				currentXYZ.Y = (float)Math.Sin(currentTheta);
				float smallRadius = (float)Math.Cos(currentTheta);

				for (int j = 0; j < m_PhiDimensions; j++)
				{
					float currentPhi = deltaPhi * j;
					currentXYZ.X = smallRadius * (float)Math.Sin(currentPhi);
					currentXYZ.Z = smallRadius * (float)Math.Cos(currentPhi);

					Ray r = new Ray(LocalOrigin, currentXYZ);
					float[] result = Intersection.GetIntersection(OuterSphere, r);

					int currentIndex = i * PhiDimensions + j;
					if (result != null && result[0] > 0)
					{
						NonHemiSphereVerts[currentIndex] = currentXYZ * result[0];
					}
					if (i != m_ThetaDimensions - 1 && j != m_PhiDimensions - 1)
					{
						SphereIndices[k++] = (short)currentIndex;
						SphereIndices[k++] = (short)(currentIndex + 1);
						SphereIndices[k++] = (short)(currentIndex + m_PhiDimensions);
						SphereIndices[k++] = (short)(currentIndex + m_PhiDimensions);
						SphereIndices[k++] = (short)(currentIndex + 1);
						SphereIndices[k++] = (short)(currentIndex + 1 + m_PhiDimensions);
					}
				}
			}

			m_SkyDomeVB = new VertexBuffer(CoCGame.Singleton.GraphicsDevice, sizeof(float) * 3 * m_NumVerts, BufferUsage.None);
			m_SkyDomeIB = new IndexBuffer(CoCGame.Singleton.GraphicsDevice, sizeof(short) * m_NumTriangles * 3, BufferUsage.None, IndexElementSize.SixteenBits);
			m_SkyDomeVB.SetData<Vector3>(NonHemiSphereVerts);
			m_SkyDomeIB.SetData<short>(SphereIndices);
		}
		#endregion

		#region RD Data

		VertexBuffer m_SkyDomeVB;
		IndexBuffer m_SkyDomeIB;

		Effect m_ScatteringEffect;
		VertexDeclaration m_VDecl;

		#endregion
	}
}